# -*- coding: utf-8 -*-
# @Author: Jerry
# @Date:   2022-03-01 16:21:23
# @Last Modified by:   Jerry
# @Last Modified time: 2022-03-09 14:17:37

# **********
# OpenCV中的图像处理 » 4_10_2_直方图-2：直方图均衡
# http://www.woshicver.com

# **********
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
import os
rootpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
datapath = os.path.join(rootpath,'data')
imgpath = lambda name: os.path.join(datapath,name)


# **********
'''目标
本节中将学习直方图均衡化的概念,并利用它来提高图像的对比度。

理论
    考虑这样一个图像，它的像素值仅局限于某个特定的值范围。
    例如，较亮的图像将把所有像素限制在高值上。
    但是一幅好的图像会有来自图像所有区域的像素。
    因此，您需要将这个直方图拉伸到两端(如下图所示，来自wikipedia)，这就是直方图均衡化的作用(简单来说)。
    这通常会提高图像的对比度。'''


# **********
'''对比度：
    - 通俗地讲就是亮暗的拉伸对比程度，通常表现了图像画质的清晰程度。
    - 這是一個量化指標，反應的是图像中细节可被肉眼分辨的程度。对比度可以通过图像的直方图反映出来。
    - 举例：
        假设我们在白天和晚上给同一个建筑物分别拍摄了一张照片，
        通过观察我们可以发现，
        晚上拍摄的照片往往比较灰暗，看上去比较费力，
        而白天拍摄的照片就不会，照片十分清晰，我们可以很好的看清楚建筑物的各个细节。
        在图像处理中我们就采用“对比度”这个概念来量化我们人眼的这种感觉。
    - 计算：4近邻计算法，8近邻计算法
      4近邻计算法：在灰度图中，一个像素点，上下左右4个像素点的值 分别减去 本像素点的值，
      所有差值的平方分别相加得到和，和除以平方的个数即得到对比度的值'''

img = cv.imread(imgpath('wiki.png'))
img=cv.cvtColor(img,cv.COLOR_BGR2RGB) # opencv是BRG模式，而plt是RGB模式，需要转化颜色才能正常显示
hist,bins = np.histogram(img.flatten(),256,[0,256])
# flatten:Return a copy of the array collapsed into one dimension
cdf = hist.cumsum()
cdf_normalized = cdf * float(hist.max()) / cdf.max()
plt.subplot(221)
plt.imshow(img)
plt.xticks([]);plt.yticks([])
plt.subplot(222)
plt.plot(cdf_normalized, color = 'b')
plt.hist(img.flatten(),256,[0,256], color = 'r')
plt.xlim([0,256])
plt.legend(('cdf','histogram'), loc = 'upper left')
plt.yticks([])
# plt.show()


# **********
'''上图计算得到的直方图，x轴值全部介于110-210之间，但是在x=140左右的地方达到最高值
而整个直方图的x值域为0-255，所以直方图位于较亮的区域，我们需要全频谱。
为此，我们需要一个转换函数，将亮区域的输入像素映射到整个区域的输出像素。
这就是直方图均衡化的作用。

现在我们找到最小的直方图值(不包括0)，并应用wiki页面中给出的直方图均衡化方程。
但我在这里用过，来自Numpy的掩码数组概念数组。
对于掩码数组，所有操作都在非掩码元素上执行。
您可以从Numpy文档中了解更多关于掩码数组的信息。

另一个重要的特征是，即使图像是一个较暗的图像(而不是我们使用的一个较亮的图像)，经过均衡后，我们将得到几乎相同的图像。
因此，这是作为一个“参考工具”，使所有的图像具有相同的照明条件。
这在很多情况下都很有用。
例如，在人脸识别中，在对人脸数据进行训练之前，对人脸图像进行直方图均衡化处理，使其具有相同的光照条件。
'''
cdf_m = np.ma.masked_equal(cdf,0)
cdf_m = (cdf_m - cdf_m.min())*255/(cdf_m.max()-cdf_m.min())
cdf = np.ma.filled(cdf_m,0).astype('uint8') # 转化函数
img2 = cdf[img]
hist,bins = np.histogram(img2.flatten(),256,[0,256])
# flatten:Return a copy of the array collapsed into one dimension
cdf = hist.cumsum()
cdf_normalized = cdf * float(hist.max()) / cdf.max()
plt.subplot(223)
plt.imshow(img2)
plt.xticks([]);plt.yticks([])
plt.subplot(224)
plt.plot(cdf_normalized, color = 'b')
plt.hist(img2.flatten(),256,[0,256], color = 'r')
plt.xlim([0,256])
plt.legend(('cdf','histogram'), loc = 'upper left')
plt.yticks([])
plt.show()

# **********
'''OpenCV中的直方图均衡
OpenCV具有执行此操作的功能cv.equalizeHist（）。
它的输入只是灰度图像，输出是我们的直方图均衡图像。
下面是一个简单的代码片段，显示了它与我们使用的同一图像的用法：'''

img = cv.imread(imgpath('wiki.png'),0)
equ = cv.equalizeHist(img) # 直方图均衡化后的图形
res = np.hstack((img,equ)) # 将2张图片水平拼接为1张图
# cv.imshow('wiki res',res)

plt.imshow(cv.cvtColor(res,cv.COLOR_BGR2RGB))
plt.show()

# 因此，现在您可以在不同的光照条件下拍摄不同的图像，对其进行均衡并检查结果。
# 当图像的直方图限制在特定区域时，直方图均衡化效果很好。
# 在直方图覆盖较大区域（即同时存在亮像素和暗像素）的强度变化较大的地方，效果不好。


# **********
'''CLAHE（对比度受限的自适应直方图均衡）
我们刚刚看到的第一个直方图均衡化考虑了图像的整体对比度。在许多情况下，这不是一个好主意。
'''
imgt = cv.imread(imgpath('tsukuba_l.png'),0)
# cv.imshow('tsukuba res',rest)
# cv.waitKey(0)
plt.subplot(321)
plt.title('original')
plt.imshow(cv.cvtColor(imgt,cv.COLOR_BGR2RGB))
plt.xticks([]);plt.yticks([])
plt.subplot(322)
plt.hist(imgt.flatten(),256,[0,256], color = 'g')
plt.yticks([])
equt = cv.equalizeHist(imgt) # 得到经过直方图均衡化处理后的图形
plt.subplot(323)
plt.title('equalizeHist')
plt.imshow(cv.cvtColor(equt,cv.COLOR_BGR2RGB))
plt.xticks([]);plt.yticks([])
plt.subplot(324)
plt.hist(equt.flatten(),256,[0,256],color='r')
plt.yticks([])
'''
在全局直方图均衡后, 背景对比度确实得到了改善。但是在两个图像中比较雕像的脸。
由于亮度过高，我们在那里丢失了大多数信息。
这是因为它的直方图不像我们在前面的案例中所看到的那样局限于特定区域

为了解决这个问题，使用了自适应直方图均衡
在这种情况下，图像被分成称为“tiles”的小块（在OpenCV中，tileSize默认为8x8）。
然后，像往常一样对这些块中的每一个进行直方图均衡。
因此，在较小的区域中，直方图将限制在一个较小的区域中（除非存在噪声）。如果有噪音，它将被放大。
为了避免这种情况，应用了对比度限制。
如果任何直方图bin超出指定的对比度限制（在OpenCV中默认为40），则在应用直方图均衡之前，将这些像素裁剪并均匀地分布到其他bin。
均衡后，要消除图块边界中的伪影，请应用双线性插值。'''
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
cl1 = clahe.apply(imgt)
plt.subplot(325)
plt.title('createCLAHE')
plt.imshow(cv.cvtColor(cl1,cv.COLOR_BGR2RGB))
plt.xticks([]);plt.yticks([])
plt.subplot(326)
plt.hist(cl1.flatten(),256,[0,256], color = 'b')
plt.yticks([])
plt.show()


# **********
# 总结：本节主要学习内容
# 1.直方图的概念、plt画直方图,plt.hist(src.flatten(),256,[0,256],color='r')
# 2.全局直方图均衡 img=cv.equalizeHist(src)
# 3.自适应直方图均衡 clahe=cv.createCLAHE(clipLimit=2.0,tileGridSize=(8,8))
#                 img=clahe.apple(src)



